﻿using Bystd.Geo.Converters;
using Bystd.Geo.Geometries;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bystd.Geo.GeoParsers.Wkb
{
    public class WkbBase
    {
        protected BitExtensions bitConvert = null;

        public WkbBase() { }

        public unsafe WkbBase(BitExtensions bitConvert)
        {
            this.bitConvert = bitConvert;
        }
    }

    public class WkbPoint : WkbBase
    {
        public Coordinate Coord { get; set; }
        public WkbPoint() { }

        public WkbPoint(byte[] array)
        {
            bitConvert = new BitExtensions((ByteOrder)array[0]);

            unsafe
            {
                fixed (byte* ptr = &array[1])
                {
                    var geometryType = (GeoType)bitConvert.ToInt32(ptr);
                    if (geometryType != GeoType.POINT) throw new Exception("geometry type is " + geometryType.ToString());

                    byte* start = ptr + 4;

                    Coord = new Coordinate()
                    {
                        X = bitConvert.ToDouble(ptr),
                        Y = bitConvert.ToDouble(ptr + 8)
                    };
                }
            }
        }

        public unsafe WkbPoint(BitExtensions bitConvert, byte* ptr)
            : base(bitConvert)
        {
            Coord = new Coordinate()
            {
                X = bitConvert.ToDouble(ptr),
                Y = bitConvert.ToDouble(ptr + 8)
            };
        }

        public static unsafe void ToWriter(Geometry geometry, byte* ptr)
        {
            var seq = geometry.GetSequence(0, 0);

            double t = seq[0].X;
            *(long*)ptr = *(long*)(&t);

            t = seq[0].Y;
            *(long*)(ptr + 8) = *(long*)(&t);
        }
        public static unsafe void ToWriter(Coordinate coordinate, byte* ptr)
        {
            double t = coordinate.X;
            *(long*)ptr = *(long*)(&t);

            t = coordinate.Y;
            *(long*)(ptr + 8) = *(long*)(&t);
        }
    }

    public class WkbMultiPoint : WkbBase, IEnumerable
    {
        public WkbPoint[] Points { get; set; }

        public Int32 Count { get; set; }

        public WkbPoint this[int index]
        {
            get { return Points[index]; }
            set { Points[index] = value; }
        }


        public WkbMultiPoint()
        { }

        public IEnumerator GetEnumerator()
        {
            return (IEnumerator)this;
        }

        public WkbMultiPoint(byte[] array)
        {
            ByteOrder byteOrder = (ByteOrder)array[0];
            bitConvert = new BitExtensions(byteOrder);

            unsafe
            {
                fixed (byte* ptr = &array[1])
                {
                    var geometryType = (GeoType)bitConvert.ToInt32(ptr);
                    if (geometryType != GeoType.MULTIPOINT) throw new Exception("geometry type is " + geometryType.ToString());

                    Count = bitConvert.ToInt32(ptr + 4);
                    Points = new WkbPoint[Count];

                    int start = 8;
                    for (int i = 0; i < Count; ++i)
                    {
                        Points[i] = new WkbPoint(bitConvert, ptr + start);
                        start += 16;
                    }
                }
            }
        }

        public unsafe WkbMultiPoint(BitExtensions bitConvert, byte* ptr)
            : base(bitConvert)
        {
            Count = bitConvert.ToInt32(ptr);
            Points = new WkbPoint[Count];

            int start = 4;
            for (int i = 0; i < Count; ++i)
            {
                WkbPoint point = new WkbPoint(bitConvert, ptr + start);
                Points[i] = point;
                start += 16;
            }
        }

        public static unsafe void ToWriter(Geometry geometry, byte* ptr)
        {
            var seq = geometry.GetSequence(0, 0);
            ToWriter(seq, ptr);
        }

        public static unsafe void ToWriter(GeoSequence seq, byte* ptr)
        {
            int c = seq.Count;
            *(int*)ptr = c;

            ptr += 4;

            for (int i = 0, step = 0; i < c; ++i)
            {
                double t = seq[i].X;
                *(long*)(ptr + step) = *(long*)(&t);

                t = seq[i].Y;
                *(long*)(ptr + step + 8) = *(long*)(&t);

                step += 16;
            }
        }

        public Coordinate[] GetCoordinates()
        {
            return Points.Select(x => x.Coord).ToArray();
        }
    }

    public class WkbLineString : WkbBase
    {
        public Int32 Count { get; set; }

        private Coordinate[] Coords { get; set; }

        public Coordinate this[int index]
        {
            get { return Coords[index]; }
            set { Coords[index] = value; }
        }

        public WkbLineString()
        { }

        private unsafe void ParserFromArray(byte* ptr)
        {
            Count = bitConvert.ToInt32(ptr);
            Coords = new Coordinate[Count];

            int start = 4;
            for (int i = 0; i < Count; ++i)
            {
                Coords[i] = new Coordinate()
                {
                    X = bitConvert.ToDouble(ptr + start),
                    Y = bitConvert.ToDouble(ptr + start + 8)
                };
                start += 16;
            }
        }

        public WkbLineString(byte[] array)
        {
            bitConvert = new BitExtensions((ByteOrder)array[0]);

            unsafe
            {
                fixed (byte* ptr = &array[1])
                {
                    Count = bitConvert.ToInt32(ptr);

                    var geometryType = (GeoType)bitConvert.ToInt32(ptr + 4);
                    if (geometryType != GeoType.LINESTRING) throw new Exception("geometry type is " + geometryType.ToString());

                    byte* start = ptr + 8;
                    ParserFromArray(start);
                }
            }
        }

        public unsafe WkbLineString(BitExtensions bitConvert, byte* ptr)
            : base(bitConvert)
        {
            ParserFromArray(ptr);
        }

        public static unsafe int ToWriter(Geometry geometry, byte* ptr)
        {
            var seq = geometry.GetSequence(0, 0);
            return ToWriter(seq, ptr);
        }

        public static unsafe int ToWriter(GeoSequence seq, byte* ptr)
        {
            int c = seq.Count;
            *(int*)ptr = c;
            int offset = 4;

            for (int i = 0; i < c; ++i)
            {
                double t = seq[i].X;
                *(long*)(ptr + offset) = *(long*)(&t);

                t = seq[i].Y;
                *(long*)(ptr + offset + 8) = *(long*)(&t);

                offset += 16;
            }
            return offset;
        }

        public Coordinate[] GetCoordinates()
        {
            return Coords;
        }
    }

    public class WkbMultiLineString : WkbBase, IEnumerable
    {
        public Int32 Count { get; set; }

        public List<WkbLineString> MultiLineString { get; set; }

        public WkbLineString this[int index]
        {
            get { return MultiLineString[index]; }
            set { MultiLineString[index] = value; }
        }

        public WkbMultiLineString()
        { }

        public IEnumerator GetEnumerator()
        {
            return (IEnumerator)this;
        }

        public WkbMultiLineString(byte[] array)
        {
            bitConvert = new BitExtensions((ByteOrder)array[0]);

            unsafe
            {
                fixed (byte* ptr = &array[1])
                {
                    Count = bitConvert.ToInt32(ptr);

                    var geometryType = (GeoType)bitConvert.ToInt32(ptr + 4);
                    if (geometryType != GeoType.MULTILINESTRING) throw new Exception("geometry type is " + geometryType.ToString());

                    byte* start = ptr + 8;
                    ParserFromArray(start);
                }
            }
        }

        public unsafe WkbMultiLineString(BitExtensions bitConvert, byte* ptr)
            : base(bitConvert)
        {
            ParserFromArray(ptr);
        }

        public static unsafe void ToWriter(Geometry geometry, byte* ptr)
        {
            var geos = geometry.GetGemoetry(0);
            ToWriter(geos, ptr);
        }

        public static unsafe void ToWriter(List<GeoSequence> geos, byte* ptr)
        {
            int count = geos.Count;
            *(int*)ptr = count;
            ptr += 4;

            for (int i = 0; i < count; ++i)
            {
                var seq = geos[i];
                int sCount = seq.Count;

                *(int*)ptr = sCount;
                ptr += 4;
                int j = 0, step = 0;

                for (; j < sCount; ++j)
                {
                    double t = seq[j].X;
                    *(long*)(ptr + step) = *(long*)(&t);

                    t = seq[j].Y;
                    *(long*)(ptr + step + 8) = *(long*)(&t);

                    step += 16;
                }
                ptr += step;
            }
        }
        public List<Coordinate[]> GetCoordinates()
        {
            List<Coordinate[]> coords = new List<Coordinate[]>(Count);
            foreach (var item in MultiLineString)
            {
                coords.Add(item.GetCoordinates());
            }
            return coords;
        }

        private unsafe void ParserFromArray(byte* ptr)
        {
            Count = bitConvert.ToInt32(ptr);
            MultiLineString = new List<WkbLineString>(Count);

            int start = 4;
            for (int j = 0; j < Count; ++j)
            {
                WkbLineString linestring = new WkbLineString(bitConvert, ptr + start);
                start += (linestring.Count * 16);
                MultiLineString.Add(linestring);
            }
        }
    }

    public class WkbPolygon : WkbBase
    {
        public Int32 Count { get; set; }

        public int Offset { get; private set; }

        private LinearRing[] Rings { get; set; }

        public LinearRing this[int index]
        {
            get { return Rings[index]; }
            set { Rings[index] = value; }
        }

        public WkbPolygon()
        { }

        public WkbPolygon(byte[] array)
        {
            bitConvert = new BitExtensions((ByteOrder)array[0]);

            unsafe
            {
                fixed (byte* ptr = &array[1])
                {
                    Count = bitConvert.ToInt32(ptr);

                    var geometryType = (GeoType)bitConvert.ToInt32(ptr + 4);
                    if (geometryType != GeoType.POLYGON) throw new Exception("geometry type is " + geometryType.ToString());

                    byte* start = ptr + 8;
                    int offset = ParserFromArray(start);
                    Offset = offset + 8;
                }
            }
        }

        private unsafe int ParserFromArray(byte* ptr)
        {
            //ring count
            Count = bitConvert.ToInt32(ptr);
            Rings = new LinearRing[Count];
            int _start = 4;

            for (int i = 0; i < Count; ++i)
            {
                int start = _start;
                int pCount = bitConvert.ToInt32(ptr + start);

                LinearRing linearing = new LinearRing(pCount);
                start += 4;

                for (int j = 0; j < pCount; ++j)
                {
                    linearing.Coords[j] = new Coordinate()
                    {
                        X = bitConvert.ToDouble(ptr + start),
                        Y = bitConvert.ToDouble(ptr + start + 8)
                    };

                    start += 16;
                }
                Rings[i] = linearing;

                _start += (4 + (pCount * 16));
            }
            return _start;
        }

        public unsafe WkbPolygon(BitExtensions bitConvert, byte* ptr)
            : base(bitConvert)
        {
            Offset = ParserFromArray(ptr);
        }

        public static unsafe int ToWriter(Geometry geometry, byte* ptr)
        {
            var geos = geometry.GetGemoetry(0);
            return ToWriter(geos, ptr);
        }

        public static unsafe int ToWriter(List<GeoSequence> geos, byte* ptr)
        {
            int count = geos.Count;
            *(int*)ptr = count;
            int offset = 4;

            for (int i = 0; i < count; ++i)
            {
                var seq = geos[i];
                int sCount = seq.Count;

                *(int*)(ptr + offset) = sCount;
                offset += 4;

                for (int j = 0; j < sCount; ++j)
                {
                    double t = seq[j].X;
                    *(long*)(ptr + offset) = *(long*)(&t);

                    t = seq[j].Y;
                    *(long*)(ptr + offset + 8) = *(long*)(&t);

                    offset += 16;
                }
            }
            return offset;
        }

        public Coordinate[] GetOuterRing()
        {
            if (Count >= 1) return Rings[0].Coords;
            return null;
        }

        public List<Coordinate[]> GetInteriorRing()
        {
            List<Coordinate[]> interior = new List<Coordinate[]>(Count - 1);
            if (Count >= 2)
            {
                for (int i = 1; i < Rings.Length; ++i)
                {
                    interior.Add(Rings[i].Coords);
                }
            }
            return interior;
        }
    }

    public class WkbMultiPolygon : WkbBase, IEnumerable
    {
        public Int32 Count { get; set; }

        public List<WkbPolygon> MultiPolygon { get; set; }

        public WkbPolygon this[int index]
        {
            get { return MultiPolygon[index]; }
            set { MultiPolygon[index] = value; }
        }

        public WkbMultiPolygon()
        { }

        public IEnumerator GetEnumerator()
        {
            foreach (var item in MultiPolygon)
            {
                yield return item;
            }
        }

        public WkbMultiPolygon(byte[] array)
        {
            bitConvert = new BitExtensions((ByteOrder)array[0]);

            unsafe
            {
                fixed (byte* ptr = &array[1])
                {
                    Count = bitConvert.ToInt32(ptr);

                    var geometryType = (GeoType)bitConvert.ToInt32(ptr + 4);
                    if (geometryType != GeoType.MULTIPOLYGON) throw new Exception("geometry type is " + geometryType.ToString());

                    byte* start = ptr + 8;
                    ParserFromArray(start);
                }
            }
        }

        public unsafe WkbMultiPolygon(BitExtensions bitConvert, byte* ptr)
            : base(bitConvert)
        {
            ParserFromArray(ptr);
        }

        public static unsafe void ToWriter(Geometry geometry, byte* ptr)
        {
            var gCount = geometry.GeometryCount;
            for (int k = 0; k < gCount; ++k)
            {
                var geos = geometry.GetGemoetry(k);

                int count = geos.Count;
                *(int*)ptr = count;
                ptr += 4;

                for (int i = 0; i < count; ++i)
                {
                    var seq = geos[i];
                    int sCount = seq.Count;

                    *(int*)ptr = sCount;
                    ptr += 4;
                    int j = 0, step = 0;

                    for (; j < sCount; ++j)
                    {
                        double t = seq[j].X;
                        *(long*)(ptr + step) = *(long*)(&t);

                        t = seq[j].Y;
                        *(long*)(ptr + step + 8) = *(long*)(&t);

                        step += 16;
                    }
                    ptr += step;
                }
            }
        }

        private unsafe void ParserFromArray(byte* ptr)
        {
            Count = bitConvert.ToInt32(ptr);
            MultiPolygon = new List<WkbPolygon>(Count);

            int start = 4;
            for (int j = 0; j < Count; ++j)
            {
                WkbPolygon polygon = new WkbPolygon(bitConvert, ptr + start);
                start += (polygon.Count * 16);
                MultiPolygon.Add(polygon);
            }
        }
    }

    public class LinearRing
    {
        public Int32 Count { get; set; }

        public Coordinate[] Coords { get; set; }

        public LinearRing() { }

        public LinearRing(Int32 Count)
        {
            this.Count = Count;
            Coords = new Coordinate[Count];
        }
    }
}
